package cn.edu.neu.lab603.des

import java.util.concurrent.atomic.AtomicInteger

import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory

import scala.collection.mutable
import scala.collection.mutable.ListBuffer

/** 离散时间系统异步仿真系统主类。
  * 该类是离散事件系统的抽象类，该类不应该被直接使用，而应该被子类继承而使用，因为这个类本身缺少参与者的上下文。如果需要在任务执行期间知道
  * 上下文中的参与者，应该在子类中声明参与者的获取方法。
  *
  * 该类只负责任务提交后的执行，具体使用方法如下：
  * <code>
  *   val a = new Simulation()
  *   a.submit(initTask);
  *   a.run();
  * </code>
  * 初始化中可以从上下文中陆续提交事件到仿真器中，然后被执行。
  *
  * @tparam T 仿真子类
  */
abstract class Simulation[T <: Simulation[T]] {

  protected lazy val logger: Logger = Logger(LoggerFactory.getLogger(getClass.getName))

  /** 获取关闭事件。 */
  protected def shutdownEvent: SimEvent[T]

  /** internal structure for task queue. */
  private class InternalTaskNode(val clock: Int, val task: SimEvent[T]) extends Comparable[InternalTaskNode] {

    /** 根据时钟周期逆序排序。 */
    override def compareTo(o: InternalTaskNode): Int = {
      val clockCmp = clock.compareTo(o.clock)
      if (clockCmp == 0) {
        task.priority.compareTo(o.task.priority)
      } else -clockCmp
    }

    /** 执行任务。 */
    def execute(context: T): Unit = task.execute(context)
  }

  private var running = false
  private val clock = new AtomicInteger
  private val tasksQueue = new mutable.PriorityQueue[InternalTaskNode]()
  private var currentTask: SimEvent[T] = _
  private val monitors = new ListBuffer[SimMonitor[T]]
  private val monitorsCache = new mutable.HashMap[SimEvent[T], Seq[SimMonitor[T]]]

  /** 获取当前运行的时钟。
    *
    * @return 获取当前运行的时钟
    */
  def getCurrentClock: Int = clock.get

  /** 测试当前仿真是否在运行状态。
    *
    * @return 是则返回真，否则返回假
    */
  def isRunning: Boolean = running

  /** 运行仿真应用。
    */
  def run(clockAtMost: Int = 1000): Unit = {
    running = true

    logger.debug("Simulation started.")

    val execNextTask: () => Unit = { () =>
      val taskNode = tasksQueue.dequeue
      assert(taskNode.clock >= clock.get, s"$taskNode: ${taskNode.clock} -> ${clock.get}")

      if (taskNode.clock - clock.get > getSimulationInterval) {
        logger.debug(s"[${clock.get}-${taskNode.clock-1}] Skipping...")
      }

      // 设置当前环境变量
      clock.getAndSet(taskNode.clock)
      currentTask = taskNode.task

      val monitorsToExec = monitorsCache.getOrElseUpdate(currentTask, monitors.filter(_.isMatch(getContext)))

      monitorsToExec.foreach(_.before(getContext))

      // 设置当前时钟周期
      clock.getAndSet(taskNode.clock)

      // 执行任务
      taskNode.task.execute(getContext)

      monitorsToExec.foreach(_.after(getContext))

      // 如果没有同时执行的，移动时钟到下一周期
      if (tasksQueue.nonEmpty) {
        val nextTask = tasksQueue.head
        if (nextTask.clock > clock.get) {
          clock.getAndAdd(getSimulationInterval)
        }
      }
    }

    do {
      execNextTask()
    } while (tasksQueue.nonEmpty && clock.get <= clockAtMost)

    // 发送结束事件
    submitTask(0, shutdownEvent)
    execNextTask()

    logger.debug("Simulation shut down.")
  }

  /** 获取当前执行的任务。
    *
    * @return 当前执行的任务
    */
  def getCurrentTask: SimEvent[T] = currentTask

  /** 在指定的时钟周期延迟里执行某个任务。
    *
    * @param delay 时钟延迟
    * @param task  仿真任务
    */
  def submitTask(delay: Int, task: SimEvent[T]): Unit = {
    val clock = getCurrentClock + delay
    val maxTimes = task.getMaxExecTimes
    if (maxTimes > -1) {
      if (maxTimes == 1) {
        if (tasksQueue.exists(c => c.clock == clock && task == c.task)) return
      } else {
        val times = tasksQueue.count(c => c.clock == clock && c.task == task)
        if (times >= maxTimes) {
          return
        }
      }
    }
    tasksQueue += new InternalTaskNode(clock, task)
  }

  /** 在下一个时钟周期里执行给定任务。
    *
    * @param task 仿真任务
    */
  def submitTaskOnNextTick(task: SimEvent[T]): Unit = submitTask(getSimulationInterval, task)

  /** 最小的仿真间隔。 */
  def getSimulationInterval: Int = 1

  /** 下一帧时间间隔的时钟。
    *
    * @return 下一帧时间间隔的时钟。
    */
  def getNextTick: Int = getCurrentClock + getSimulationInterval

  /** 下 k 帧的时钟周期。
    *
    * @param k 第 k 个时钟间隔。
    * @return 下 k 帧时间间隔的时钟
    */
  def getNextKTick(k: Int): Int = getCurrentClock + k * getSimulationInterval

  /** 添加监视器。
    *
    * @param monitor 监视器对象
    */
  def addMonitor(monitor: SimMonitor[T]): Unit = {
    monitors += monitor
    monitorsCache.clear()
  }

  /** 获得监视器列表。
    *
    * @return 监视器列表
    */
  def getMonitors: Seq[SimMonitor[T]] = monitors

  /** 获取当前的上下文。
    *
    * @return 上下文对象
    */
  private def getContext: T = this.asInstanceOf[T]
}
